using System;
using System.Collections.Generic;
using System.Text;

namespace PDSClient.Utils.Shared
{
    /// <summary>
    /// completed.
    /// </summary>
    public sealed class CompactId
    {
        public const int MAX_SIZE = 8 + 0x0f;

        private readonly byte[] canonicalId; /* was final */

        private readonly byte[] externalForm; /* was final */

        public CompactId(byte[] id) 
        {
            if (id == null)
                throw new NullReferenceException("null id");
            else if (id.Length == 0)
                throw new ArgumentException("zero length id");
            else if (id.Length > MAX_SIZE)
                throw new ArgumentException("id length exceeds maximum size of " + MAX_SIZE);

            canonicalId = StripLeadingZeroBytes(id);
            externalForm = ToExternalForm(canonicalId);
        }
        public CompactId(byte[] id, byte[] ext) 
        {
            canonicalId = id;
            externalForm = ext;
        }

        public byte[] Id { get { return canonicalId; } }

        public byte[] ExternalForm { get { return externalForm; } }

        public int ExternalFormByteCount { get { return externalForm.Length; } }

        public static CompactId FromExternalForm(byte[] externalForm)
        {
            if (externalForm == null)
                throw new NullReferenceException("null external form");
            else if (externalForm.Length < 2)
                throw new ArgumentException("invalid external form; must have 2 or more bytes");
            else if (externalForm.Length > MAX_SIZE + 1)
                throw new ArgumentException("invalid external form; > 23 bytes unsupported");

            int size = GetExternalFormByteCount(externalForm[0]);
            
            if (size != externalForm.Length)
                throw new ArgumentException("invalid external form; should have " + size + " bytes");

            byte[] id;
            if (size <= 8)
            {
                int firstByte = externalForm[0] & 0x3f;
                int first = 0;

                //find first non-zero byte in external form
                if (firstByte == 0)
                {
                    for (first = 1; first < externalForm.Length && externalForm[first] == 0;
                        first++)
                        ;
                }

                if (first == externalForm.Length)
                {
                    //all bytes are zero, so first byte is last byte
                    first = externalForm.Length - 1;
                }
                id = new byte[externalForm.Length - first];
                Array.Copy(externalForm, first, id, 0, id.Length);
                if (first == 0)
                    id[0] = (byte)firstByte;
            }
            else
            {
                id = new byte[externalForm.Length - 1];
                Array.Copy(externalForm, 1, id, 0, id.Length);
            }
            return new CompactId(id, externalForm);
        }

        private static int GetExternalFormByteCount(byte lengthByte)
        {
            switch (lengthByte & 0xc0)
            {
                case 0x00:
                    return 2;
                case 0x40:
                    return 4;
                case 0x80:
                    return 8;

                default: /* 0xc0 */
                    if ((lengthByte & 0x30) == 0)
                    {
                        return 9 + (lengthByte & 0x0f);
                    }
                    else
                    {
                        throw new ArgumentException("unsupported id format; lengthByte: " +
                            lengthByte);
                    }
            }
        }
        public void PutCompactId(MessageBuffer buf) 
        {
            if (buf.Capacity - buf.Position < externalForm.Length)
                throw new ArgumentException("buffer size insufficient");

            buf.PutBytes(externalForm);
        }

        public static CompactId GetCompactId(MessageBuffer buf)
        {
            int bufSize = buf.Limit - buf.Position;
            if (bufSize == 0)
                throw new ArgumentException("empty buffer");

            byte lengthByte = buf.GetByte();
            int size = GetExternalFormByteCount(lengthByte);
            if (bufSize < size)
                throw new ArgumentException("buffer size insufficient");

            byte[] externalForm = new byte[size];
            externalForm[0] = lengthByte;
            for (int i = 1; i < size; i++)
                externalForm[i] = buf.GetByte();

            return CompactId.FromExternalForm(externalForm);
        }

        public override bool Equals(object obj)
        {
            if (this == obj) 
                return true;

            if (!(obj is CompactId))
                return false;

            CompactId thatCompactId = (CompactId)obj;
            return Array.Equals(canonicalId, thatCompactId.canonicalId) &&
                Array.Equals(externalForm, thatCompactId.externalForm);
        }

        public override int GetHashCode() 
        {
            return canonicalId.GetHashCode();
        }

        private byte[] StripLeadingZeroBytes(byte[] id)
        {
            //find first nonzero byte
            int first = 0;
            for (; first < id.Length && id[first] == 0; first++) ;

            if (first == id.Length) // all bytes are zero, so first byte is last byte
                first = id.Length - 1;

            byte[] canonicalId = new byte[id.Length - first];
            Array.Copy(id, first, canonicalId, 0, canonicalId.Length);
            return canonicalId;
        }

        private byte[] ToExternalForm(byte[] id)
        {
            //TODO :
            	//assert id != null;
	            //assert id.length != 0;
                //assert id.length <= MAX_SIZE;

            //find bit count
            int first = 0;
            int b = id[first];
            int zeroBits = 0;
            for (; (zeroBits < 8) && ((b & 0x80) == 0); b <<= 1, zeroBits++) ;

            int bitCount = ((id.Length - first - 1) << 3) + (8 - zeroBits);

            //determine external form's byte count and the mask for most
            //significant two bits of first byte.
            int mask = 0x00;
            int size;
            if (bitCount <= 14)
                size = 2;
            else if (bitCount <= 30)
            {
                size = 4;
                mask = 0x40;
            }
            else if (bitCount <= 62)
            {
                size = 8;
                mask = 0x80;
            }
            else
            {
                size = id.Length + 1;
                mask = 0xc0 + id.Length - 8;
            }

            //copy id into destination byte array and apply mask

            byte[] external = new byte[size];
            for (int i = id.Length - 1, e = size - 1; i >= first; i--, e--)
                external[e] = id[i];

            external[0] |= (byte)mask;

            return external;
        }


    }
}
